/*-------------------------------------------------------------------------
 *
 * pg_visibility.c
 *	  显示可见性图信息和页面级可见性位
 *
 * Copyright (c) 2016-2022, PostgreSQL Global Development Group
 *
 *	  contrib/pg_visibility/pg_visibility.c
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/visibilitymap.h"
#include "access/xloginsert.h"
#include "catalog/pg_type.h"
#include "catalog/storage_xlog.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/smgr.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"

PG_MODULE_MAGIC;

typedef struct vbits
{
	BlockNumber next;
	BlockNumber count;
	uint8		bits[FLEXIBLE_ARRAY_MEMBER];
} vbits;

typedef struct corrupt_items
{
	BlockNumber next;
	BlockNumber count;
	ItemPointer tids;
} corrupt_items;

PG_FUNCTION_INFO_V1(pg_visibility_map);
PG_FUNCTION_INFO_V1(pg_visibility_map_rel);
PG_FUNCTION_INFO_V1(pg_visibility);
PG_FUNCTION_INFO_V1(pg_visibility_rel);
PG_FUNCTION_INFO_V1(pg_visibility_map_summary);
PG_FUNCTION_INFO_V1(pg_check_frozen);
PG_FUNCTION_INFO_V1(pg_check_visible);
PG_FUNCTION_INFO_V1(pg_truncate_visibility_map);

static TupleDesc fc_pg_visibility_tupdesc(bool fc_include_blkno, bool fc_include_pd);
static vbits *fc_collect_visibility_data(Oid fc_relid, bool fc_include_pd);
static corrupt_items *fc_collect_corrupt_items(Oid fc_relid, bool fc_all_visible,
											bool fc_all_frozen);
static void fc_record_corrupt_item(corrupt_items *fc_items, ItemPointer fc_tid);
static bool fc_tuple_all_visible(HeapTuple fc_tup, TransactionId fc_OldestXmin,
							  Buffer fc_buffer);
static void fc_check_relation_relkind(Relation fc_rel);

/*
 * 关系单个块的可见性映射信息。
 *
 * 注意：VM代码将默默地为映射末尾后的页面返回零，因此我们允许探测到MaxBlockNumber，而不考虑实际的关系大小。
 */
Datum pg_visibility_map(PG_FUNCTION_ARGS)
{
	Oid			fc_relid = PG_GETARG_OID(0);
	int64		fc_blkno = PG_GETARG_INT64(1);
	int32		fc_mapbits;
	Relation	fc_rel;
	Buffer		fc_vmbuffer = InvalidBuffer;
	TupleDesc	fc_tupdesc;
	Datum		fc_values[2];
	bool		fc_nulls[2];

	fc_rel = relation_open(fc_relid, AccessShareLock);

	/* 只有某些relkind具有可见性映射 */
	fc_check_relation_relkind(fc_rel);

	if (fc_blkno < 0 || fc_blkno > MaxBlockNumber)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("invalid block number")));

	fc_tupdesc = fc_pg_visibility_tupdesc(false, false);
	MemSet(fc_nulls, 0, sizeof(fc_nulls));

	fc_mapbits = (int32) visibilitymap_get_status(fc_rel, fc_blkno, &fc_vmbuffer);
	if (fc_vmbuffer != InvalidBuffer)
		ReleaseBuffer(fc_vmbuffer);
	fc_values[0] = BoolGetDatum((fc_mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0);
	fc_values[1] = BoolGetDatum((fc_mapbits & VISIBILITYMAP_ALL_FROZEN) != 0);

	relation_close(fc_rel, AccessShareLock);

	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(fc_tupdesc, fc_values, fc_nulls)));
}

/*
 * 关系单个块的可见性映射信息，以及该块的页面级信息。
 */
Datum pg_visibility(PG_FUNCTION_ARGS)
{
	Oid			fc_relid = PG_GETARG_OID(0);
	int64		fc_blkno = PG_GETARG_INT64(1);
	int32		fc_mapbits;
	Relation	fc_rel;
	Buffer		fc_vmbuffer = InvalidBuffer;
	Buffer		fc_buffer;
	Page		fc_page;
	TupleDesc	fc_tupdesc;
	Datum		fc_values[3];
	bool		fc_nulls[3];

	fc_rel = relation_open(fc_relid, AccessShareLock);

	/* 只有某些relkind具有可见性映射 */
	fc_check_relation_relkind(fc_rel);

	if (fc_blkno < 0 || fc_blkno > MaxBlockNumber)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("invalid block number")));

	fc_tupdesc = fc_pg_visibility_tupdesc(false, true);
	MemSet(fc_nulls, 0, sizeof(fc_nulls));

	fc_mapbits = (int32) visibilitymap_get_status(fc_rel, fc_blkno, &fc_vmbuffer);
	if (fc_vmbuffer != InvalidBuffer)
		ReleaseBuffer(fc_vmbuffer);
	fc_values[0] = BoolGetDatum((fc_mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0);
	fc_values[1] = BoolGetDatum((fc_mapbits & VISIBILITYMAP_ALL_FROZEN) != 0);

	/* 在这里我们必须明确检查关系大小... */
	if (fc_blkno < RelationGetNumberOfBlocks(fc_rel))
	{
		fc_buffer = ReadBuffer(fc_rel, fc_blkno);
		LockBuffer(fc_buffer, BUFFER_LOCK_SHARE);

		fc_page = BufferGetPage(fc_buffer);
		fc_values[2] = BoolGetDatum(PageIsAllVisible(fc_page));

		UnlockReleaseBuffer(fc_buffer);
	}
	else
	{
		/* 与vismap一样，对于EOF之后的页面默默返回0 */
		fc_values[2] = BoolGetDatum(false);
	}

	relation_close(fc_rel, AccessShareLock);

	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(fc_tupdesc, fc_values, fc_nulls)));
}

/*
 * 关系中每个块的可见性映射信息。
 */
Datum pg_visibility_map_rel(PG_FUNCTION_ARGS)
{
	FuncCallContext *fc_funcctx;
	vbits	   *fc_info;

	if (SRF_IS_FIRSTCALL())
	{
		Oid			fc_relid = PG_GETARG_OID(0);
		MemoryContext fc_oldcontext;

		fc_funcctx = SRF_FIRSTCALL_INIT();
		fc_oldcontext = MemoryContextSwitchTo(fc_funcctx->multi_call_memory_ctx);
		fc_funcctx->tuple_desc = fc_pg_visibility_tupdesc(true, false);
		/* collect_visibility_data将验证relkind */
		fc_funcctx->user_fctx = fc_collect_visibility_data(fc_relid, false);
		MemoryContextSwitchTo(fc_oldcontext);
	}

	fc_funcctx = SRF_PERCALL_SETUP();
	fc_info = (vbits *) fc_funcctx->user_fctx;

	if (fc_info->next < fc_info->count)
	{
		Datum		fc_values[3];
		bool		fc_nulls[3];
		HeapTuple	fc_tuple;

		MemSet(fc_nulls, 0, sizeof(fc_nulls));
		fc_values[0] = Int64GetDatum(fc_info->next);
		fc_values[1] = BoolGetDatum((fc_info->bits[fc_info->next] & (1 << 0)) != 0);
		fc_values[2] = BoolGetDatum((fc_info->bits[fc_info->next] & (1 << 1)) != 0);
		fc_info->next++;

		fc_tuple = heap_form_tuple(fc_funcctx->tuple_desc, fc_values, fc_nulls);
		SRF_RETURN_NEXT(fc_funcctx, HeapTupleGetDatum(fc_tuple));
	}

	SRF_RETURN_DONE(fc_funcctx);
}

/*
 * 关系中每个块的可见性映射信息，以及每个块的页面级信息。
 */
Datum pg_visibility_rel(PG_FUNCTION_ARGS)
{
	FuncCallContext *fc_funcctx;
	vbits	   *fc_info;

	if (SRF_IS_FIRSTCALL())
	{
		Oid			fc_relid = PG_GETARG_OID(0);
		MemoryContext fc_oldcontext;

		fc_funcctx = SRF_FIRSTCALL_INIT();
		fc_oldcontext = MemoryContextSwitchTo(fc_funcctx->multi_call_memory_ctx);
		fc_funcctx->tuple_desc = fc_pg_visibility_tupdesc(true, true);
		/* collect_visibility_data将验证relkind */
		fc_funcctx->user_fctx = fc_collect_visibility_data(fc_relid, true);
		MemoryContextSwitchTo(fc_oldcontext);
	}

	fc_funcctx = SRF_PERCALL_SETUP();
	fc_info = (vbits *) fc_funcctx->user_fctx;

	if (fc_info->next < fc_info->count)
	{
		Datum		fc_values[4];
		bool		fc_nulls[4];
		HeapTuple	fc_tuple;

		MemSet(fc_nulls, 0, sizeof(fc_nulls));
		fc_values[0] = Int64GetDatum(fc_info->next);
		fc_values[1] = BoolGetDatum((fc_info->bits[fc_info->next] & (1 << 0)) != 0);
		fc_values[2] = BoolGetDatum((fc_info->bits[fc_info->next] & (1 << 1)) != 0);
		fc_values[3] = BoolGetDatum((fc_info->bits[fc_info->next] & (1 << 2)) != 0);
		fc_info->next++;

		fc_tuple = heap_form_tuple(fc_funcctx->tuple_desc, fc_values, fc_nulls);
		SRF_RETURN_NEXT(fc_funcctx, HeapTupleGetDatum(fc_tuple));
	}

	SRF_RETURN_DONE(fc_funcctx);
}

/*
 * 计算在某个特定关系的可见性映射中所有可见和所有冻结页面的数量。
 */
Datum pg_visibility_map_summary(PG_FUNCTION_ARGS)
{
	Oid			fc_relid = PG_GETARG_OID(0);
	Relation	fc_rel;
	BlockNumber fc_nblocks;
	BlockNumber fc_blkno;
	Buffer		fc_vmbuffer = InvalidBuffer;
	int64		fc_all_visible = 0;
	int64		fc_all_frozen = 0;
	TupleDesc	fc_tupdesc;
	Datum		fc_values[2];
	bool		fc_nulls[2];

	fc_rel = relation_open(fc_relid, AccessShareLock);

	/* 只有某些relkind具有可见性映射 */
	fc_check_relation_relkind(fc_rel);

	fc_nblocks = RelationGetNumberOfBlocks(fc_rel);

	for (fc_blkno = 0; fc_blkno < fc_nblocks; ++fc_blkno)
	{
		int32		fc_mapbits;

		/* 确保我们是可中断的。 */
		CHECK_FOR_INTERRUPTS();

		/* 获取映射信息。 */
		fc_mapbits = (int32) visibilitymap_get_status(fc_rel, fc_blkno, &fc_vmbuffer);
		if ((fc_mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0)
			++fc_all_visible;
		if ((fc_mapbits & VISIBILITYMAP_ALL_FROZEN) != 0)
			++fc_all_frozen;
	}

	/* 清理。 */
	if (fc_vmbuffer != InvalidBuffer)
		ReleaseBuffer(fc_vmbuffer);
	relation_close(fc_rel, AccessShareLock);

	fc_tupdesc = CreateTemplateTupleDesc(2);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 1, "all_visible", INT8OID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 2, "all_frozen", INT8OID, -1, 0);
	fc_tupdesc = BlessTupleDesc(fc_tupdesc);

	MemSet(fc_nulls, 0, sizeof(fc_nulls));
	fc_values[0] = Int64GetDatum(fc_all_visible);
	fc_values[1] = Int64GetDatum(fc_all_frozen);

	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(fc_tupdesc, fc_values, fc_nulls)));
}

/*
 * 返回在可见性映射中标记为全冻结的页面中存在的非冻结元组的TID。我们希望没有人会找到任何，但可能会有错误、数据库损坏等。
 */
Datum pg_check_frozen(PG_FUNCTION_ARGS)
{
	FuncCallContext *fc_funcctx;
	corrupt_items *fc_items;

	if (SRF_IS_FIRSTCALL())
	{
		Oid			fc_relid = PG_GETARG_OID(0);
		MemoryContext fc_oldcontext;

		fc_funcctx = SRF_FIRSTCALL_INIT();
		fc_oldcontext = MemoryContextSwitchTo(fc_funcctx->multi_call_memory_ctx);
		/* collect_corrupt_items将验证relkind */
		fc_funcctx->user_fctx = fc_collect_corrupt_items(fc_relid, false, true);
		MemoryContextSwitchTo(fc_oldcontext);
	}

	fc_funcctx = SRF_PERCALL_SETUP();
	fc_items = (corrupt_items *) fc_funcctx->user_fctx;

	if (fc_items->next < fc_items->count)
		SRF_RETURN_NEXT(fc_funcctx, PointerGetDatum(&fc_items->tids[fc_items->next++]));

	SRF_RETURN_DONE(fc_funcctx);
}

/*
 * 返回在可见性映射中标记为全可见的页面中存在的非全可见元组的TID。我们希望没有人会找到任何，但可能会有错误、数据库损坏等。
 */
Datum pg_check_visible(PG_FUNCTION_ARGS)
{
	FuncCallContext *fc_funcctx;
	corrupt_items *fc_items;

	if (SRF_IS_FIRSTCALL())
	{
		Oid			fc_relid = PG_GETARG_OID(0);
		MemoryContext fc_oldcontext;

		fc_funcctx = SRF_FIRSTCALL_INIT();
		fc_oldcontext = MemoryContextSwitchTo(fc_funcctx->multi_call_memory_ctx);
		/* collect_corrupt_items将验证relkind */
		fc_funcctx->user_fctx = fc_collect_corrupt_items(fc_relid, true, false);
		MemoryContextSwitchTo(fc_oldcontext);
	}

	fc_funcctx = SRF_PERCALL_SETUP();
	fc_items = (corrupt_items *) fc_funcctx->user_fctx;

	if (fc_items->next < fc_items->count)
		SRF_RETURN_NEXT(fc_funcctx, PointerGetDatum(&fc_items->tids[fc_items->next++]));

	SRF_RETURN_DONE(fc_funcctx);
}

/*
 * 移除关系的可见性映射分支。如果可见性映射代码中确实存在需要重建VM的错误，这为用户提供了一种比关闭服务器和手动删除文件更干净的方法。
 *
 * 这是RelationTruncate的简化版本。
 */
Datum pg_truncate_visibility_map(PG_FUNCTION_ARGS)
{
	Oid			fc_relid = PG_GETARG_OID(0);
	Relation	fc_rel;
	ForkNumber	fc_fork;
	BlockNumber fc_block;
	BlockNumber fc_old_block;

	fc_rel = relation_open(fc_relid, AccessExclusiveLock);

	/* 只有某些relkind具有可见性映射 */
	fc_check_relation_relkind(fc_rel);

	/* 强制重置缓存的文件大小 */
	RelationGetSmgr(fc_rel)->smgr_cached_nblocks[VISIBILITYMAP_FORKNUM] = InvalidBlockNumber;

	/* 在进入临界区之前计算新旧大小。 */
	fc_fork = VISIBILITYMAP_FORKNUM;
	fc_block = visibilitymap_prepare_truncate(fc_rel, 0);
	fc_old_block = BlockNumberIsValid(fc_block) ? smgrnblocks(RelationGetSmgr(fc_rel), fc_fork) : 0;

	/*
	 * WAL-日志记录、缓冲区丢弃、文件截断必须是原子的，并且都在检查点的一侧。有关讨论，请参见RelationTruncate()。
	 */
	Assert((MyProc->delayChkptFlags & (DELAY_CHKPT_START | DELAY_CHKPT_COMPLETE)) == 0);
	MyProc->delayChkptFlags |= DELAY_CHKPT_START | DELAY_CHKPT_COMPLETE;
	START_CRIT_SECTION();

	if (RelationNeedsWAL(fc_rel))
	{
		XLogRecPtr	fc_lsn;
		xl_smgr_truncate fc_xlrec;

		fc_xlrec.blkno = 0;
		fc_xlrec.rnode = fc_rel->rd_node;
		fc_xlrec.flags = SMGR_TRUNCATE_VM;

		XLogBeginInsert();
		XLogRegisterData((char *) &fc_xlrec, sizeof(fc_xlrec));

		fc_lsn = XLogInsert(RM_SMGR_ID,
						 XLOG_SMGR_TRUNCATE | XLR_SPECIAL_REL_UPDATE);
		XLogFlush(fc_lsn);
	}

	if (BlockNumberIsValid(fc_block))
		smgrtruncate2(RelationGetSmgr(fc_rel), &fc_fork, 1, &fc_old_block, &fc_block);

	END_CRIT_SECTION();
	MyProc->delayChkptFlags &= ~(DELAY_CHKPT_START | DELAY_CHKPT_COMPLETE);

	/*
	 * 立即释放锁，而不是在提交时。
	 *
	 * 如果在提交之前释放锁，将会产生问题，特别是如果这个
	 * 截断操作发送了任何事务性无效消息。其他
	 * 后端在我们此处释放锁与我们最终在提交时发送消息之间的时间窗口内
	 * 可能会能够锁定关系而不处理这些消息。然而，
	 * 我们目前只发送非事务性的 smgr 无效消息，
	 * 它将在 smgr_truncate 中立即被发布到共享内存中。因此，
	 * 在这里应该没有竞争条件。
	 *
	 * 在这里提前释放锁是可取的，原因在于
	 * 有可能有人需要使用它来一次性清除许多
	 * 可见性映射的分叉。如果我们不能在
	 * 提交时释放锁，执行此操作的事务将在同一时间积累
	 * 对所有这些关系的 AccessExclusiveLocks，这是
	 * 不可取的。然而，如果这被证明是不安全的，我们可能别无选择...
	 */
	relation_close(fc_rel, AccessExclusiveLock);

	/* 没有什么可以返回。 */
	PG_RETURN_VOID();
}

/*
 * 帮助函数，用于构造我们需要的特定
 * 调用的 TupleDesc。
 */
static TupleDesc fc_pg_visibility_tupdesc(bool fc_include_blkno, bool fc_include_pd)
{
	TupleDesc	fc_tupdesc;
	AttrNumber	fc_maxattr = 2;
	AttrNumber	fc_a = 0;

	if (fc_include_blkno)
		++fc_maxattr;
	if (fc_include_pd)
		++fc_maxattr;
	fc_tupdesc = CreateTemplateTupleDesc(fc_maxattr);
	if (fc_include_blkno)
		TupleDescInitEntry(fc_tupdesc, ++fc_a, "blkno", INT8OID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, ++fc_a, "all_visible", BOOLOID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, ++fc_a, "all_frozen", BOOLOID, -1, 0);
	if (fc_include_pd)
		TupleDescInitEntry(fc_tupdesc, ++fc_a, "pd_all_visible", BOOLOID, -1, 0);
	Assert(fc_a == fc_maxattr);

	return BlessTupleDesc(fc_tupdesc);
}

/*
 * 收集关于关系的可见性数据。
 *
 * 检查 relid 的 relkind，如果关系没有
 * 可见性映射，则将抛出错误。
 */
static vbits *
fc_collect_visibility_data(Oid fc_relid, bool fc_include_pd)
{
	Relation	fc_rel;
	BlockNumber fc_nblocks;
	vbits	   *fc_info;
	BlockNumber fc_blkno;
	Buffer		fc_vmbuffer = InvalidBuffer;
	BufferAccessStrategy fc_bstrategy = GetAccessStrategy(BAS_BULKREAD);

	fc_rel = relation_open(fc_relid, AccessShareLock);

	/* 只有某些relkind具有可见性映射 */
	fc_check_relation_relkind(fc_rel);

	fc_nblocks = RelationGetNumberOfBlocks(fc_rel);
	fc_info = palloc0(offsetof(vbits, bits) + fc_nblocks);
	fc_info->next = 0;
	fc_info->count = fc_nblocks;

	for (fc_blkno = 0; fc_blkno < fc_nblocks; ++fc_blkno)
	{
		int32		fc_mapbits;

		/* 确保我们是可中断的。 */
		CHECK_FOR_INTERRUPTS();

		/* 获取映射信息。 */
		fc_mapbits = (int32) visibilitymap_get_status(fc_rel, fc_blkno, &fc_vmbuffer);
		if ((fc_mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0)
			fc_info->bits[fc_blkno] |= (1 << 0);
		if ((fc_mapbits & VISIBILITYMAP_ALL_FROZEN) != 0)
			fc_info->bits[fc_blkno] |= (1 << 1);

		/*
		 * 页面级数据需要读取每个块，因此只有在
		 * 调用者需要时才获取。 使用缓冲访问策略，以防止
		 * 缓存污染。
		 */
		if (fc_include_pd)
		{
			Buffer		fc_buffer;
			Page		fc_page;

			fc_buffer = ReadBufferExtended(fc_rel, MAIN_FORKNUM, fc_blkno, RBM_NORMAL,
										fc_bstrategy);
			LockBuffer(fc_buffer, BUFFER_LOCK_SHARE);

			fc_page = BufferGetPage(fc_buffer);
			if (PageIsAllVisible(fc_page))
				fc_info->bits[fc_blkno] |= (1 << 2);

			UnlockReleaseBuffer(fc_buffer);
		}
	}

	/* 清理。 */
	if (fc_vmbuffer != InvalidBuffer)
		ReleaseBuffer(fc_vmbuffer);
	relation_close(fc_rel, AccessShareLock);

	return fc_info;
}

/*
 * 返回可见性映射信息与页面上元组的状态不匹配的项目列表。
 *
 * 如果传递了 true 的 all_visible，这将包括所有在可见性映射中
 * 标记为全可见的页面上的项目，但实际上似乎并不是
 * 全可见的。
 *
 * 如果传递了 true 的 all_frozen，这将包括所有在标记为全冻结的页面上的项目
 * 但实际上似乎并不是冻结的。
 *
 * 检查 relid 的 relkind，如果关系没有
 * 可见性映射，则将抛出错误。
 */
static corrupt_items *
fc_collect_corrupt_items(Oid fc_relid, bool fc_all_visible, bool fc_all_frozen)
{
	Relation	fc_rel;
	BlockNumber fc_nblocks;
	corrupt_items *fc_items;
	BlockNumber fc_blkno;
	Buffer		fc_vmbuffer = InvalidBuffer;
	BufferAccessStrategy fc_bstrategy = GetAccessStrategy(BAS_BULKREAD);
	TransactionId fc_OldestXmin = InvalidTransactionId;

	fc_rel = relation_open(fc_relid, AccessShareLock);

	/* 只有某些relkind具有可见性映射 */
	fc_check_relation_relkind(fc_rel);

	if (fc_all_visible)
		fc_OldestXmin = GetOldestNonRemovableTransactionId(fc_rel);

	fc_nblocks = RelationGetNumberOfBlocks(fc_rel);

	/*
	 * 猜测初始数组大小。我们不期望有很多损坏的元组，
	 * 所以从一个小数组开始。这个函数使用“next”字段来跟踪
	 * 我们可以存储项目的下一个偏移量（这与
	 * 到目前为止找到的项目数量相同），使用“count”字段来跟踪
	 * 分配的条目的数量。在返回之前，我们将重新使用这些字段。
	 */
	fc_items = palloc0(sizeof(corrupt_items));
	fc_items->next = 0;
	fc_items->count = 64;
	fc_items->tids = palloc(fc_items->count * sizeof(ItemPointerData));

	/* 遍历关系中的每个块。 */
	for (fc_blkno = 0; fc_blkno < fc_nblocks; ++fc_blkno)
	{
		bool		fc_check_frozen = false;
		bool		fc_check_visible = false;
		Buffer		fc_buffer;
		Page		fc_page;
		OffsetNumber fc_offnum,
					fc_maxoff;

		/* 确保我们是可中断的。 */
		CHECK_FOR_INTERRUPTS();

		/* 使用可见性映射来决定是否检查这个页面。 */
		if (fc_all_frozen && VM_ALL_FROZEN(fc_rel, fc_blkno, &fc_vmbuffer))
			fc_check_frozen = true;
		if (fc_all_visible && VM_ALL_VISIBLE(fc_rel, fc_blkno, &fc_vmbuffer))
			fc_check_visible = true;
		if (!fc_check_visible && !fc_check_frozen)
			continue;

		/* 读取并锁定页面。 */
		fc_buffer = ReadBufferExtended(fc_rel, MAIN_FORKNUM, fc_blkno, RBM_NORMAL,
									fc_bstrategy);
		LockBuffer(fc_buffer, BUFFER_LOCK_SHARE);

		fc_page = BufferGetPage(fc_buffer);
		fc_maxoff = PageGetMaxOffsetNumber(fc_page);

		/*
		 * 可见性地图位可能在我们获取页面锁时发生了变化。重新检查以避免返回虚假结果。
		 */
		if (fc_check_frozen && !VM_ALL_FROZEN(fc_rel, fc_blkno, &fc_vmbuffer))
			fc_check_frozen = false;
		if (fc_check_visible && !VM_ALL_VISIBLE(fc_rel, fc_blkno, &fc_vmbuffer))
			fc_check_visible = false;
		if (!fc_check_visible && !fc_check_frozen)
		{
			UnlockReleaseBuffer(fc_buffer);
			continue;
		}

		/* 遍历页面上的每个元组。 */
		for (fc_offnum = FirstOffsetNumber;
			 fc_offnum <= fc_maxoff;
			 fc_offnum = OffsetNumberNext(fc_offnum))
		{
			HeapTupleData fc_tuple;
			ItemId		fc_itemid;

			fc_itemid = PageGetItemId(fc_page, fc_offnum);

			/* 未使用或重定向的行指针无关紧要。 */
			if (!ItemIdIsUsed(fc_itemid) || ItemIdIsRedirected(fc_itemid))
				continue;

			/* 死亡行指针既不是全可见的也不是冻结的。 */
			if (ItemIdIsDead(fc_itemid))
			{
				ItemPointerSet(&(fc_tuple.t_self), fc_blkno, fc_offnum);
				fc_record_corrupt_item(fc_items, &fc_tuple.t_self);
				continue;
			}

			/* 初始化一个 HeapTupleData 结构以进行以下检查。 */
			ItemPointerSet(&(fc_tuple.t_self), fc_blkno, fc_offnum);
			fc_tuple.t_data = (HeapTupleHeader) PageGetItem(fc_page, fc_itemid);
			fc_tuple.t_len = ItemIdGetLength(fc_itemid);
			fc_tuple.t_tableOid = fc_relid;

			/*
			 * 如果我们正在检查页面是否全可见，那么我们期望
			 * 元组应该是全可见的。
			 */
			if (fc_check_visible &&
				!fc_tuple_all_visible(&fc_tuple, fc_OldestXmin, fc_buffer))
			{
				TransactionId fc_RecomputedOldestXmin;

				/*
				 * 自从我们计算了 OldestXmin 以来已经过去了一段时间，因此，
				 * 现实中这个元组可能是全可见的，即使根据我们
				 * 之前计算的值并非如此。让我们计算一个新值，以便
				 * 确定是否存在问题。
				 *
				 * 从并发的角度来看，在我们独占锁定缓冲区时
				 * 重新获取 ProcArrayLock 有点糟糕，但这应该是安全的
				 * 避免死锁，因为
				 * GetOldestNonRemovableTransactionId() 肯定不应该获得缓冲区锁。
				 * 并且这不应该经常发生，因此值得小心以避免误报。
				 */
				fc_RecomputedOldestXmin = GetOldestNonRemovableTransactionId(fc_rel);

				if (!TransactionIdPrecedes(fc_OldestXmin, fc_RecomputedOldestXmin))
					fc_record_corrupt_item(fc_items, &fc_tuple.t_self);
				else
				{
					fc_OldestXmin = fc_RecomputedOldestXmin;
					if (!fc_tuple_all_visible(&fc_tuple, fc_OldestXmin, fc_buffer))
						fc_record_corrupt_item(fc_items, &fc_tuple.t_self);
				}
			}

			/*
			 * 如果我们正在检查页面是否全冻结，我们期望元组处于
			 * 从不需要冻结的状态。
			 */
			if (fc_check_frozen)
			{
				if (heap_tuple_needs_eventual_freeze(fc_tuple.t_data))
					fc_record_corrupt_item(fc_items, &fc_tuple.t_self);
			}
		}

		UnlockReleaseBuffer(fc_buffer);
	}

	/* 清理。 */
	if (fc_vmbuffer != InvalidBuffer)
		ReleaseBuffer(fc_vmbuffer);
	relation_close(fc_rel, AccessShareLock);

	/*
	 * 在返回之前，重新利用字段以匹配调用者的期望。
	 * next 现在是下一个应读取（而不是写入）的项目，而
	 * count 现在是我们写入的项目数量（而不是我们分配的数量）。
	 */
	fc_items->count = fc_items->next;
	fc_items->next = 0;

	return fc_items;
}

/*
 * 记住一个损坏的项目。
 */
static void fc_record_corrupt_item(corrupt_items *fc_items, ItemPointer fc_tid)
{
	/* 如果需要，扩大输出数组。 */
	if (fc_items->next >= fc_items->count)
	{
		fc_items->count *= 2;
		fc_items->tids = repalloc(fc_items->tids,
							   fc_items->count * sizeof(ItemPointerData));
	}
	/* 并添加新项目 */
	fc_items->tids[fc_items->next++] = *fc_tid;
}

/*
 * 检查一个元组是否相对于给定的 OldestXmin 值全可见。
 * 缓冲区应包含该元组，并且应被锁定和固定。
 */
static bool fc_tuple_all_visible(HeapTuple fc_tup, TransactionId fc_OldestXmin, Buffer fc_buffer)
{
	HTSV_Result fc_state;
	TransactionId fc_xmin;

	fc_state = HeapTupleSatisfiesVacuum(fc_tup, fc_OldestXmin, fc_buffer);
	if (fc_state != HEAPTUPLE_LIVE)
		return false;			/* 全可见意味着活的 */

	/*
	 * lazy_scan_heap 和 heap_page_is_all_visible 俩者都不会将页面
	 * 标记为全可见，除非每个元组都被提示已提交。然而，这些提示
	 * 位在崩溃后可能会丢失，因此我们无法确定它们是否
	 * 会在这里被设置。所以只需检查 xmin。
	 */

	fc_xmin = HeapTupleHeaderGetXmin(fc_tup->t_data);
	if (!TransactionIdPrecedes(fc_xmin, fc_OldestXmin))
		return false;			/* xmin 不够旧，无法供所有人看到 */

	return true;
}

/*
 * check_relation_relkind - 便利例程，用于检查关系
 * 是否是调用者支持的 relkind
 */
static void fc_check_relation_relkind(Relation fc_rel)
{
	if (!RELKIND_HAS_TABLE_AM(fc_rel->rd_rel->relkind))
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("relation \"%s\" is of wrong relation kind",
						RelationGetRelationName(fc_rel)),
				 errdetail_relkind_not_supported(fc_rel->rd_rel->relkind)));
}
